节点脚本(Node Scripts)
节点脚本(Node Scripts)可用于渲染形状、图像、文本、画板等内容。
创建节点脚本
脚本结构(Anatomy)
-- Define the script's data and inputs.
type MyNode = {}
-- Called once when the script initializes.
function init(self: MyNode): boolean
return true
end
-- Called every frame to advance the simulation.
-- 'seconds' is the elapsed time since the previous frame.
function advance(self: MyNode, seconds: number): boolean
return false
end
-- Called when any input value changes.
function update(self: MyNode) end
-- Called every frame (after advance) to render the content.
function draw(self: MyNode, renderer: Renderer) end
-- Return a factory function that Rive uses to build the Node instance.
return function(): Node<MyNode>
return {
init = init,
advance = advance,
update = update,
draw = draw,
}
end
绘制(Drawing)
节点脚本允许在场景中按程序方式绘制图形。
function rectangle(self: Rectangle)
-- Update the path with current width and height
self.path:reset()
local halfWidth = self.width / 2
local halfHeight = self.height / 2
-- Draw rectangle centered at origin
self.path:moveTo(Vector.xy(-halfWidth, -halfHeight))
self.path:lineTo(Vector.xy(halfWidth, -halfHeight))
self.path:lineTo(Vector.xy(halfWidth, halfHeight))
self.path:lineTo(Vector.xy(-halfWidth, halfHeight))
self.path:close()
-- Update paint color
self.paint.color = self.color
end
function draw(self: Rectangle, renderer: Renderer)
renderer:drawPath(self.path, self.paint)
end
完整绘图 API 参考:Path API
常见模式(Common Patterns)
运行时实例化组件(Instantiating Components)
该模式需要了解 数据绑定(Data Binding)、组件(Components) 与 脚本输入(Script Inputs)。
type Enemy = {
artboard: Artboard<Data.Enemy>,
position: Vector,
}
export type MyGame = {
-- This is the component that we will dynamically add to our scene
-- See: https://rive.app/docs/scripting/script-inputs
enemy: Input<Artboard<Data.Enemy>>,
enemies: { Enemy },
}
function createEnemy(self: MyGame)
-- Create an instance of the artboard
local enemy = self.enemy:instance()
-- Keep track of all enemies in self.enemies
local entry: Enemy = {
artboard = enemy,
position = Vector.xy(0, 0),
}
table.insert(self.enemies, entry)
end
function init(self: MyGame)
createEnemy(self)
return true
end
function advance(self: MyGame, seconds: number)
-- Advance the artboard of each enemy
for _, enemy in self.enemies do
enemy.artboard:advance(seconds)
end
return true
end
function draw(self: MyGame, renderer: Renderer)
-- draw each enemy
for _, enemy in self.enemies do
renderer:save()
enemy.artboard:draw(renderer)
renderer:restore()
end
end
return function(): Node<MyGame>
return {
init = init,
advance = advance,
draw = draw,
enemy = late(),
enemies = {},
}
end
固定步进(Fixed-Step Advance)
不同设备帧率不同,若直接用可变帧时间更新,会导致运动不一致。
可使用固定时间步(fixed timestep)推进模拟,保证行为稳定。
--- Fixed Timestep Advance
--- Keeps movement consistent across different frame rates
--- by advancing the simulation in fixed time steps.
export type CarGame = {
speed: Input<number>,
accumulator: number,
fixedStep: Input<number>,
direction: number,
currentX: number,
currentY: number,
}
-- Prevent the script from running too many catch-up steps
-- after a long pause or frame drop.
local MAX_STEPS = 5
function advance(self: CarGame, seconds: number): boolean
-- Add the time since the last frame to the accumulator.
self.accumulator += seconds
local dt = self.fixedStep
local steps = 0
-- Run the simulation in small, fixed steps.
-- If the frame took longer than one step, multiple steps may run this frame.
while self.accumulator >= dt and steps < MAX_STEPS do
-- Move forward by speed * time.
-- Using a fixed dt keeps movement stable even if the frame rate changes.
self.currentX += self.speed * math.cos(self.direction) * dt
self.currentY += self.speed * math.sin(self.direction) * dt
-- Subtract one fixed step from the accumulator
-- and repeat until we've caught up to real time.
self.accumulator -= dt
steps += 1
end
return true
end
-- Create a new instance of the CarGame script with default values.
-- The simulation runs 60 fixed steps per second.
return function(): Node<CarGame>
return {
speed = 100,
accumulator = 0,
direction = 0,
fixedStep = 1 / 60,
currentX = 0,
currentY = 0,
}
end